/*
 * 版权信息 : @ 聚均科技
 */
package cn.xiaoke.util;


import cn.xiaoke.util.interc.Converter;
import org.apache.ibatis.javassist.*;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author xiaokedamowang
 * @version 1.0
 * @date 2022/1/18 14:00
 */
public class SsistBeanUtil {

    public static final ConcurrentHashMap<String, Converter> map = new ConcurrentHashMap<>();
    @SuppressWarnings("all")
    public static <T, D> D converter(T t, Class<D> dClazz) {
//        final String converterClassName = "com.fusionfintrade.util.convert.ads";
        final String converterClassName = "com.fusionfintrade.util.convert." + StringUtils.capitalize(t.getClass().getSimpleName())
                + "ConverterTo" + StringUtils.capitalize(dClazz.getSimpleName());
        final Converter beanConverter = map.computeIfAbsent(converterClassName, (k) -> {
            Converter converter = null;
            ClassPool pool = ClassPool.getDefault();
            CtClass cc;
            try {
                cc = pool.get(converterClassName);
                final Class<?> aClass;
                try {
                    aClass = cc.toClass();
                    converter = (Converter) aClass.newInstance();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return converter;
            } catch (NotFoundException e) {
                try {
                    cc = pool.makeClass(converterClassName);
                    cc.addInterface(pool.get(Converter.class.getName()));
                    final Class<?> tClazz = t.getClass();
                    StringBuilder methodBody = new StringBuilder();
                    methodBody.append("public Object converter(" + "Object" + " ot){\r\n");
                    methodBody.append(tClazz.getName()).append(" t = (").append(tClazz.getName()).append(")ot;\r\n");
                    methodBody.append(dClazz.getName()).append(" d = new ").append(dClazz.getName()).append("();\r\n");

                    final Map<String, List<Method>> tMap = getMethodMap(tClazz);
                    final Map<String, List<Method>> dMap = getMethodMap(dClazz);

                    tMap.forEach((tk, tv) -> {
                        dMap.computeIfPresent(tk, (dk, dv) -> {
                            final Map<String, Method> tGroup = tv.stream().collect(Collectors.toMap((method -> method.getName().substring(0, 3)), Function.identity()));
                            final Map<String, Method> dGroup = dv.stream().collect(Collectors.toMap((method -> method.getName().substring(0, 3)), Function.identity()));
                            methodBody.append("d.").append(dGroup.get("set").getName()).append("(t.").append(tGroup.get("get").getName()).append("());\r\n");
                            return dv;
                        });
                    });
                    methodBody.append("return d;\r\n}");
                    final CtMethod make = CtNewMethod.make(methodBody.toString(), cc);
                    cc.addMethod(make);
                    final Class<?> aClass = cc.toClass();
                    converter = (Converter) aClass.newInstance();
                    return converter;
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            return null;
        });
        final Object ret = beanConverter.converter(t);
        return (D) ret;
    }



    private static Map<String, List<Method>> getMethodMap(Class<?> clazz) {
        final List<Method> allMethod = getAllMethod(clazz);
        final Set<Method> methods = filterGetSetMethods(allMethod);
        return methodGorupBy(methods);
    }

    private static Map<String, List<Method>> methodGorupBy(Set<Method> set) {
        final Map<String, List<Method>> map = set.stream().collect(Collectors.groupingBy((method) -> {
            final String methodName = method.getName();
            return methodName.substring(3);
        }));
        map.entrySet().removeIf(entry -> entry.getValue().size() != 2);
        return map;
    }

    private static Set<Method> filterGetSetMethods(List<Method> methods) {
        return methods.stream().filter(method -> {
                    final int modifiers = method.getModifiers();
                    return !Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers);
                })
                .filter(method -> {
                    final String methodName = method.getName();
                    return methodName.startsWith("get") || methodName.startsWith("set");
                }).collect(Collectors.toSet());
    }

    private static List<Method> getAllMethod(Class<?> clazz) {
        Class<?> aClass = clazz;
        final List<Method> methods = new ArrayList<>();
        while (aClass != null) {
            methods.addAll(Arrays.asList(aClass.getDeclaredMethods()));
            aClass = aClass.getSuperclass();
        }
        return methods;
    }


}
